﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using RFEngine.Entities;
using RFEngine.Graphics.Interfaces;

namespace RFEngine.Graphics
{
    public class Text : PositionedObject, IDrawableEntity, ITextureable
    {
        private const float LINE_BUFFER = 1.6678f;

        #region Fields

        private int _CharacterCount = 0;
        private int _TotalCharacterCount = 0;
        private int _LineCount = 0;
        private string _DisplayText = "";

        private bool _EnableUpdates = true;
        private bool _EnableDrawing = true;
        private bool _UpdateNeeded = false;
        private bool _TextureStateChanged;
        private bool _ColorChanged;

        private float _Red = 1.0f;
        private float _Green = 1.0f;
        private float _Blue = 1.0f;
        private float _Alpha = 1.0f;

        private float _AlphaRate;
        private float _RedRate;
        private float _GreenRate;
        private float _BlueRate;

        private float _XScaleRate;
        private float _YScaleRate;

        private float _LineSpacing = 1.0f;
        private Vector2 _MinVector = Vector2.Zero;
        private Vector2 _MaxVector = Vector2.Zero;

        private Font _Font;
        private Color _Color = Color.White;
        private Vector2 _Scale = Vector2.One;

        private Vector3 _LastPosition = Vector3.Zero;
        private Vector3 _LastScale = Vector3.One;
        private Vector3 _LastRotation = Vector3.Zero;

        private Matrix _TranslationMatrix = Matrix.Identity;
        private Matrix _RotationMatrix = Matrix.Identity;
        private Matrix _RotationXMatrix = Matrix.Identity;
        private Matrix _RotationYMatrix = Matrix.Identity;
        private Matrix _RotationZMatrix = Matrix.Identity;
        private Matrix _ScaleMatrix = Matrix.Identity;
        private Matrix _WorldMatrix = Matrix.Identity;

        private TextureState _TextureState = TextureState.Normal;
        private VerticalAlignment _VerticalAlignment = VerticalAlignment.Center;

        private Vector2 _LastCharacterScale = Vector2.One;
        private Vector3 _LastCharacterPosition = Vector3.Zero;
        private TexturedQuad _Character = new TexturedQuad();

        private SpriteVertex[] _VerticesForDrawing;

        #region Temporary Field Members

        // These members are used to cut down on the memory consumption when doing matrix calculations.

        private Vector2 _TempVector2 = Vector2.Zero;
        private Vector3 _TempVector3 = Vector3.Zero;

        #endregion

        #endregion

        #region Properties

        /// <summary>
        /// Gets or Sets a value indicating this object should update.
        /// </summary>
        public bool EnableUpdates
        {
            get { return _EnableUpdates; }
            set { _EnableUpdates = value; }
        }

        /// <summary>
        /// Gets or Sets a value indicating this object should draw.
        /// </summary>
        public bool EnableDrawing
        {
            get { return _EnableDrawing; }
            set { _EnableDrawing = value; }
        }

        /// <summary>
        /// Get or Sets the text to draw.
        /// </summary>
        public string DisplayText
        {
            get { return _DisplayText; }
            set
            {
                if (_DisplayText != value)
                {
                    _DisplayText = value;
                    _TextureStateChanged = true;
                }
            }
        }

        /// <summary>
        /// Gets or Sets the red color component to use when tinting this object.
        /// </summary>
        public virtual float Red
        {
            get { return _Red; }
            set
            {
                _Red = value;

                if (_Red > 1.0f)
                    _Red = 1.0f;

                else if (_Red < 0.0f)
                    _Red = 0.0f;

                _Color.PackedValue = (_Color.PackedValue & 0xff00ffff) | ((uint)(255f * _Red) << 0x10);
                _ColorChanged = true;
            }
        }

        /// <summary>
        /// Gets or Sets the rate of change for the red color component in units per second.
        /// </summary>
        public virtual float RedRate
        {
            get { return _RedRate; }
            set { _RedRate = value; }
        }

        /// <summary>
        /// Gets or Sets the green color component to use when tinting this object.
        /// </summary>
        public virtual float Green
        {
            get { return _Green; }
            set
            {
                _Green = value;

                if (_Green > 1.0f)
                    _Green = 1.0f;

                else if (_Green < 0.0f)
                    _Green = 0.0f;

                _Color.PackedValue = (_Color.PackedValue & 0xffff00ff) | ((uint)(255f * _Green) << 0x8);
                _ColorChanged = true;
            }
        }

        /// <summary>
        /// Gets or Sets the rate of change for the green color component in units per second.
        /// </summary>
        public virtual float GreenRate
        {
            get { return _GreenRate; }
            set { _GreenRate = value; }
        }

        /// <summary>
        /// Gets or Sets the blue color component to use when tinting this object.
        /// </summary>
        public virtual float Blue
        {
            get { return _Blue; }
            set
            {
                _Blue = value;
                if (_Blue > 1.0f)
                    _Blue = 1.0f;

                if (Blue < 0.0f)
                    _Blue = 0.0f;

                _Color.PackedValue = (_Color.PackedValue & 0xffffff00) | ((uint)(255f * _Blue));
                _ColorChanged = true;
            }
        }

        /// <summary>
        /// Gets or Sets the rate of change for the blue color component in units per second.
        /// </summary>
        public virtual float BlueRate
        {
            get { return _BlueRate; }
            set { _BlueRate = value; }
        }

        /// <summary>
        /// Gets or Sets the alpha color component to use when tinting this object.
        /// </summary>
        public virtual float Alpha
        {
            get { return _Alpha; }
            set
            {
                _Alpha = value;

                if (_Alpha > 1.0f)
                    _Alpha = 1.0f;

                else if (_Alpha < 0.0f)
                    _Alpha = 0.0f;

                _Color.PackedValue = (_Color.PackedValue & 0x00ffffff) | ((uint)(255f * _Alpha) << 0x18);
                _ColorChanged = true;
            }
        }

        /// <summary>
        /// Gets or Sets the rate of change for the alpha color component in units per second.
        /// </summary>
        public virtual float AlphaRate
        {
            get { return _AlphaRate; }
            set { _AlphaRate = value; }
        }

        /// <summary>
        /// Gets or Sets the scale of this object along the x-axis in world space.
        /// </summary>
        public virtual float XScale
        {
            get { return _Scale.X; }
            set { _Scale.X = value; }
        }

        /// <summary>
        /// Gets or Sets the rate at which this object is scaled along the x-axis in world space in units per second.
        /// </summary>
        public virtual float XScaleRate
        {
            get { return _XScaleRate; }
            set { _XScaleRate = value; }
        }

        /// <summary>
        /// Gets or Sets the scale of this object along the y-axis in world space.
        /// </summary>
        public virtual float YScale
        {
            get { return _Scale.Y; }
            set { _Scale.Y = value; }
        }

        /// <summary>
        /// Gets or Sets the rate at which this object is scaled along the y-axis in world space in units per second.
        /// </summary>
        public virtual float YScaleRate
        {
            get { return _YScaleRate; }
            set { _YScaleRate = value; }
        }

        /// <summary>
        /// Gets the color tint of this object.
        /// </summary>
        public virtual Color Color
        {
            get { return _Color; }
        }

        /// <summary>
        /// Gets the scaling vector of this object in world space.
        /// </summary>
        public virtual Vector2 Scale
        {
            get { return _Scale; }
        }

        /// <summary>
        /// Gets or Sets the texture used by this object.
        /// </summary>
        public virtual Texture2D Texture
        {
            get { return _Font.Texture; }
        }

        /// <summary>
        /// Gets or Set how this texture is drawn.
        /// </summary>
        public virtual TextureState TextureState
        {
            get { return _TextureState; }
            set
            {
                _TextureState = value;
                _TextureStateChanged = true;
            }
        }

        /// <summary>
        /// Gets or Sets the alignment how this object is drawn around the vertical axis.
        /// </summary>
        public VerticalAlignment VerticalAlignment
        {
            get { return _VerticalAlignment; }
            set
            {
                _VerticalAlignment = value;
                _TextureStateChanged = true;
            }
        }

        #region Interface Members

        float ITextureable.TopTextureCoordinate
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        float ITextureable.BottomTextureCoordinate
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        float ITextureable.LeftTextureCoordinate
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        float ITextureable.RightTextureCoordinate
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        Texture2D ITextureable.Texture
        {
            get { return _Font.Texture; }
            set { throw new NotSupportedException(); }
        }

        #endregion

        #endregion

        #region Methods

        #region Constructor(s)

        /// <summary>
        /// Construct(s) a new instance of this object.
        /// </summary>
        /// <param name="font">The font for this object to use.</param>
        /// <param name="displayText">A string value this object will display when drawn</param>
        public Text(Font font, string displayText)
        {
            _Font = font;
            _DisplayText = displayText;
            _VerticesForDrawing = new SpriteVertex[0];
            GenerateDisplayText();
        }

        #endregion

        /// <summary>
        /// Method used for updating this object.
        /// </summary>
        /// <param name="updateTime">The amount of time that has passed since the last update.</param>
        public void Update(float updateTime)
        {
            if (_RotationRate.X != 0 || _RotationRate.Y != 0 || _RotationRate.Z != 0)
            {
                XRotation += _RotationRate.X * updateTime * 360;
                YRotation += _RotationRate.Y * updateTime * 360;
                ZRotation += _RotationRate.Z * updateTime * 360;
            }

            if (_Velocity.X != 0 || _Velocity.Y != 0 || _Velocity.Z != 0)
            {
                X += _Velocity.X * updateTime;
                Y += _Velocity.Y * updateTime;
                Z += _Velocity.Z * updateTime;
            }

            if (_RedRate != 0 || _GreenRate != 0 || _BlueRate != 0 || _AlphaRate != 0)
            {
                Red += _RedRate * updateTime;
                Green += _GreenRate * updateTime;
                Blue += _BlueRate * updateTime;
                Alpha += _AlphaRate * updateTime;
            }

            if (_TextureStateChanged)
                _UpdateNeeded = true;

            if (_LastScale.X != _Scale.X || _LastScale.Y != _Scale.Y)
                UpdateScale();

            if (_LastRotation != _Rotation)
                UpdateRotation();

            if (_LastPosition != Position)
                UpdatePosition();

            if (_UpdateNeeded)
                UpdateVertices();

            if (_ColorChanged)
                UpdateColor();

            _LastPosition = Position;
            _LastRotation = Rotation;
            _LastScale.X = _Scale.X;
            _LastScale.Y = _Scale.Y;       
        }

        /// <summary>
        /// Method used for drawing this object to the screen.
        /// </summary>
        /// <param name="effect">The Effect to use when drawing this object.</param>
        public void Draw(Effect effect) 
        {
            int primitiveCount = _TotalCharacterCount * 2;

            if (primitiveCount > 0)
                effect.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, _VerticesForDrawing, 0, primitiveCount);
        }

        #region Private Methods

        /// <summary>
        /// Method used for generating the display text, that will be drawn to the screen.
        /// </summary>
        private void GenerateDisplayText()
        {
            if (_DisplayText == "")
                _DisplayText = " ";

            _DisplayText = _DisplayText.Replace("\t", "    ");
            _DisplayText = _DisplayText.Replace('\n', '\r');
            _DisplayText = _DisplayText.Replace("\r\r", "\r");            

            if (_VerticesForDrawing.Length < _DisplayText.Length * 6)
                Array.Resize(ref _VerticesForDrawing, _DisplayText.Length * 6);

            _LastCharacterPosition = GetVector3(0.0f, 0.0f, 0.0f);
            _LastCharacterScale = GetVector2(0.0f, 0.0f);

            _LineCount = 0;
            _CharacterCount = 0;
            _TotalCharacterCount = 0;

            foreach (char chr in _DisplayText)
            {
                if (chr == '\r')
                {
                    _LineCount++;

                    GenerateLineText();

                    _MaxVector.X = 0.0f;
                    _MinVector.X = 0.0f;

                    _LastCharacterPosition.X = 0;
                    _LastCharacterPosition.Y -= _LineSpacing * LINE_BUFFER;
                    _LastCharacterScale.X = 0;

                    _TotalCharacterCount += _CharacterCount;
                    _CharacterCount = 0;

                    continue;
                }

                GetCharacter(chr);

                for (int i = 0; i < 6; i++)
                {
                    _VerticesForDrawing[((_TotalCharacterCount + _CharacterCount) * 6) + i] = _Character.VerticesForDrawing[i];

                    if (_Character.VerticesForDrawing[i].Position.X > _MaxVector.X)
                        _MaxVector.X = _Character.VerticesForDrawing[i].Position.X;

                    if (_Character.VerticesForDrawing[i].Position.X < _MinVector.X)
                        _MinVector.X = _Character.VerticesForDrawing[i].Position.X;

                    if (_Character.VerticesForDrawing[i].Position.Y > _MaxVector.Y)
                        _MaxVector.Y = _Character.VerticesForDrawing[i].Position.Y;

                    if (_Character.VerticesForDrawing[i].Position.Y < _MinVector.Y)
                        _MinVector.Y = _Character.VerticesForDrawing[i].Position.Y;
                }

                _LastCharacterPosition = _Character.Position;
                _LastCharacterScale = _Character.Scale;
                
                _CharacterCount++;
            }

            GenerateLineText();

            _TotalCharacterCount += _CharacterCount;

            _TextureStateChanged = false;
        }

        private void GenerateLineText()
        {
            for (int i = _TotalCharacterCount * 6; i < (_TotalCharacterCount + _CharacterCount) * 6; i++)
            {
                GetVector3(_VerticesForDrawing[i].Position.X, _VerticesForDrawing[i].Position.Y, 0.0f);

                switch (_VerticalAlignment)
                {
                    case VerticalAlignment.Right:
                        _TempVector3.X -= _MaxVector.X;
                        break;

                    case VerticalAlignment.Center:
                        _TempVector3.X -= _MaxVector.X / 2.0f;
                        break;
                }

                _VerticesForDrawing[i].Position = _TempVector3;
            }
        }

        private void GetCharacter(char chr)
        {
            _Character.TopTextureCoordinate = _Font.FontDescription.CharacterInfo[chr].TopTextureCoordinate;
            _Character.BottomTextureCoordinate = _Font.FontDescription.CharacterInfo[chr].BottomTextureCoordinate;
            _Character.LeftTextureCoordinate = _Font.FontDescription.CharacterInfo[chr].LeftTextureCoordinate;
            _Character.RightTextureCoordinate = _Font.FontDescription.CharacterInfo[chr].RightTextureCoordinate;
            _Character.UpdateTextureCoords(_TextureState);

            GetVector2
                (
                    _Font.FontDescription.CharacterInfo[chr].XScale * _Scale.X,
                    _Font.FontDescription.CharacterInfo[chr].YScale * _Scale.Y
                );

            if (_LineCount >= 1)
                GetVector3
                    (
                        _LastCharacterPosition.X + _LastCharacterScale.X + _TempVector2.X,
                        _LastCharacterPosition.Y + _LastCharacterScale.Y - _TempVector2.Y,
                        0.0f
                    );
            else
                GetVector3
                    (
                        _LastCharacterPosition.X + _LastCharacterScale.X + _TempVector2.X,
                        0.0f,
                        0.0f
                    );

            _Character.Scale = _TempVector2;
            _Character.Position = _TempVector3;
        }

        /// <summary>
        /// Method for forcing an update on the _TranslationMatrix.
        /// </summary>
        private void UpdatePosition()
        {
            GetVector3(_Position.X, _Position.Y, _Position.Z);
            Matrix.CreateTranslation(ref _TempVector3, out _TranslationMatrix);
            _UpdateNeeded = true;
        }

        /// <summary>
        /// Method for forcing an update on the rotation matricies.
        /// </summary>
        private void UpdateRotation()
        {
            if (_Rotation.X > 360.0f)
                _Rotation.X -= 360.0f;

            else if (_Rotation.X < 0.0f)
                _Rotation.X += 360.0f;

            if (_Rotation.Y > 360.0f)
                _Rotation.Y -= 360.0f;

            else if (_Rotation.Y < 0.0f)
                _Rotation.Y += 360.0f;

            Matrix.CreateFromYawPitchRoll(MathHelper.ToRadians(_Rotation.Y), MathHelper.ToRadians(_Rotation.X), MathHelper.ToRadians(_Rotation.Z), out _RotationMatrix);

            _UpdateNeeded = true;
        }

        /// <summary>
        /// Method for forcing an update on the _ScaleMatrix.
        /// </summary>
        private void UpdateScale()
        {
            GetVector3(_Scale.X , _Scale.Y, 1.0f);
            Matrix.CreateScale(ref _TempVector3, out _ScaleMatrix);
            _UpdateNeeded = true;
        }

        /// <summary>
        /// Method for forcing the color change on all drawing vertices.
        /// </summary>
        private void UpdateColor()
        {
            for (int i = 0; i < _TotalCharacterCount * 6; i++)
                _VerticesForDrawing[i].Color = _Color;

            _ColorChanged = false;
        }

        /// <summary>
        /// Method for forcing an update on the drawing vertices.
        /// </summary>
        private void UpdateVertices()
        {
            GenerateDisplayText();

            _WorldMatrix = _ScaleMatrix * _RotationMatrix * _TranslationMatrix;

            for (int i = 0; i < _VerticesForDrawing.Length; i++)
            {
                GetVector3(_VerticesForDrawing[i].Position.X, _VerticesForDrawing[i].Position.Y, Z);
                _VerticesForDrawing[i].Position = Vector3.Transform(_TempVector3, _WorldMatrix);
            }

            _UpdateNeeded = false;
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Method for getting a new Vector2 without creating a new instance.
        /// </summary>
        /// <param name="x">The value along the x-axis in world space.</param>
        /// <param name="y">The value along the y-axis in world space.</param>
        /// <returns>A position in world space.</returns>
        protected Vector2 GetVector2(float x, float y)
        {
            _TempVector2.X = x;
            _TempVector2.Y = y;

            return _TempVector2;
        }

        /// <summary>
        /// Method for getting a new Vector2 without creating a new instance.
        /// </summary>
        /// <param name="x">The value along the x-axis in world space.</param>
        /// <param name="y">The value along the y-axis in world space.</param>
        /// <param name="z">The value along the z-axis in world space.</param>
        /// <returns>A position in world space.</returns>                
        protected Vector3 GetVector3(float x, float y, float z)
        {
            _TempVector3.X = x;
            _TempVector3.Y = y;
            _TempVector3.Z = z;

            return _TempVector3;
        }

        #endregion

        #endregion

        #region IDrawableEntity Members


        public bool IsInView(ref Matrix view, ref Matrix projection)
        {
            return true;
        }

        public bool IsInView(ref BoundingFrustum boundingFrustum)
        {
            return true;
        }

        #endregion
    }
}
